﻿// Autore: Alessio Parma

using System;
using System.Collections.Generic;
using System.Data.Objects.DataClasses;
using System.Linq;

namespace RecipesManager.Data.Implementation
{
    sealed partial class Recipe : IRecipe
    {
        readonly IIngredientQuantities _ingredients;
        readonly RecipeSteps _originalSteps;
        readonly RecipeSteps _steps;
        readonly IRecipeTrials _trials;

        public Recipe()
        {
            _ingredients = new IngredientQuantities(() => IngredientQuantities, () => Id);
            _originalSteps = new RecipeSteps(() => Id, () => Steps, original: true);
            _steps = new RecipeSteps(() => Id, () => Steps, original: false);
            _trials = new RecipeTrials(() => Id, () => Trials);
        }

        #region IRecipe Members

        IEnumerable<IEvent> IRecipe.Events
        {
            get { return EventServings.Select(e => e.Event); }
        }

        IEnumerable<IOtherRating<IGuest, FoodRating>> IRecipe.GuestRatings
        {
            get { return GuestRatings; }
        }

        IIngredientQuantities IRecipe.Ingredients
        {
            get { return _ingredients; }
        }

        MeasureFamily IRecipe.MeasureFamily
        {
            get { return (MeasureFamily) MeasureFamily; }
        }

        string INamedEntity.Name
        {
            get { return Name; }
        }

        IEnumerable<IIngredientQuantity> IRecipe.OriginalIngredients
        {
            get { return OriginalIngredientQuantities; }
        }

        IEnumerable<IRecipeStep> IRecipe.OriginalSteps
        {
            get { return _originalSteps; }
        }

        IRecipeSteps IRecipe.Steps
        {
            get { return _steps; }
        }

        IEnumerable<IRecipe> IRecipe.SubRecipeOf
        {
            get
            {
                return from step in Manager.Recipes.SelectMany(r => r.Steps)
                       where step.Type == StepType.SubRecipe && step.SubRecipe.Id == Id
                       select step.Recipe;
            }
        }

        IRecipeTrials IRecipe.Trials
        {
            get { return _trials; }
        }

        public bool Equals(IRecipe other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Id == other.Id;
        }

        public void MakeOriginal()
        {
            OriginalPrepHours = PrepHours;
            OriginalPrepMinutes = PrepMinutes;
            OriginalServings = Servings;
            OriginalIngredientQuantities.Clear();
            foreach (var iq in IngredientQuantities)
                OriginalIngredientQuantities.Add(OriginalIngredientQuantity.New(Id, iq));
            foreach (var s in (this as IRecipe).Steps.ToList())
            {
                if (s.Type == StepType.Generic)
                    _originalSteps.AddGeneric(s.Description, s.Resources, s.StepHours, s.StepMinutes);
                else
                    _originalSteps.AddSubRecipe(s.SubRecipe.Name, s.Servings);
            }
            HasOriginal = true;
            Manager.SaveChanges();
        }

        public void Update(string name, string source, MeasureFamily measureFamily, int? prepHours, int? prepMinutes,
                           int? servings)
        {
            Name = name.ToKey();
            Source = source;
            MeasureFamily = (byte) measureFamily;
            PrepHours = prepHours;
            PrepMinutes = prepMinutes;
            Servings = servings;
            Manager.SaveChanges();
        }

        #endregion

        public static Recipe New(string name, string source, MeasureFamily measureFamily, int? prepHours,
                                 int? prepMinutes, int? servings)
        {
            name = name.ToKey();
            var recipe = CreateRecipe(0, name, source, (byte) measureFamily);
            recipe.PrepHours = prepHours;
            recipe.PrepMinutes = prepMinutes;
            recipe.Servings = servings;
            return recipe;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            return ReferenceEquals(this, obj) || Equals(obj as IRecipe);
        }

        public override int GetHashCode()
        {
            return Id;
        }

        public override string ToString()
        {
            return string.Format("Recipe(Name: {0})", Name);
        }
    }

    sealed class RecipeObjects : NamedObjects<Recipe, IRecipe>, IRecipes
    {
        public RecipeObjects() : base(Manager.RecipeSet)
        {
        }

        #region IRecipes Members

        public IRecipe Add(string name, string source, MeasureFamily measureFamily, int? prepHours, int? prepMinutes,
                           int? servings)
        {
            return Add(Recipe.New(name, source, measureFamily, prepHours, prepMinutes, servings));
        }

        public override void Remove(int id)
        {
            RemoveRecipe(GetEntity(id));
        }

        public override void Remove(string name)
        {
            RemoveRecipe(GetEntity(name));
        }

        #endregion

        void RemoveRecipe(Recipe r)
        {
            var ir = r as IRecipe;
            ir.Steps.Clear();
            foreach (var id in ir.Trials.Select(t => t.Id).ToList())
                ir.Trials.Remove(id);
            Remove(r);
        }
    }

    sealed partial class GuestRecipeRating : IRating<IRecipe, FoodRating>, IOtherRating<IGuest, FoodRating>
    {
        #region IOtherRating<IGuest,FoodRating> Members

        IGuest IOtherRating<IGuest, FoodRating>.Entity
        {
            get { return Guest; }
        }

        int IOtherRating<IGuest, FoodRating>.Id
        {
            get { return GuestId; }
        }

        string IOtherRating<IGuest, FoodRating>.Name
        {
            get { return Guest.Name; }
        }

        FoodRating IOtherRating<IGuest, FoodRating>.Rating
        {
            get { return (FoodRating) Rating; }
        }

        #endregion

        #region IRating<IRecipe,FoodRating> Members

        IRecipe IRating<IRecipe, FoodRating>.Entity
        {
            get { return Recipe; }
        }

        int IEntity.Id
        {
            get { return RecipeId; }
        }

        string INamedEntity.Name
        {
            get { return Recipe.Name; }
        }

        FoodRating IRating<IRecipe, FoodRating>.Rating
        {
            get { return (FoodRating) Rating; }
        }

        public void Update(FoodRating rating)
        {
            Rating = (byte) rating;
            Manager.SaveChanges();
        }

        #endregion
    }

    sealed class RecipeRatings : NamedLinkSet<GuestRecipeRating, IRating<IRecipe, FoodRating>>,
                                 IRatings<IRecipe, FoodRating>
    {
        public RecipeRatings(Func<EntityCollection<GuestRecipeRating>> items, Func<int> idGetter)
            : base(items, idGetter)
        {
        }

        #region IRatings<IRecipe,FoodRating> Members

        public INamedEntities<IRecipe> Entities
        {
            get { return Manager.Recipes; }
        }

        public IRating<IRecipe, FoodRating> Add(string name, FoodRating rating)
        {
            return Add(GuestRecipeRating.CreateGuestRecipeRating(IdGetter(), Entities[name].Id, (byte) rating));
        }

        #endregion
    }

    sealed partial class RecipeStep : IRecipeStep
    {
        public bool Original
        {
            get { return Step.Original; }
        }

        #region IRecipeStep Members

        string IRecipeStep.Description
        {
            get { return Step.Description; }
        }

        int IEntity.Id
        {
            get { return Step.Id; }
        }

        IRecipe IRecipeStep.Recipe
        {
            get { return Recipe; }
        }

        string IRecipeStep.Resources
        {
            get { return Step.Resources; }
        }

        int? IRecipeStep.Servings
        {
            get { return Step.Servings; }
        }

        int? IRecipeStep.StepHours
        {
            get { return Step.StepHours; }
        }

        int? IRecipeStep.StepMinutes
        {
            get { return Step.StepMinutes; }
        }

        IRecipe IRecipeStep.SubRecipe
        {
            get { return Step.SubRecipe; }
        }

        StepType IRecipeStep.Type
        {
            get { return (StepType) Step.Type; }
        }

        public void Update(string description, string resources, int? stepHours, int? stepMinutes)
        {
            // Generic step fields
            Step.Description = description;
            Step.Resources = resources;
            Step.StepHours = stepHours;
            Step.StepMinutes = stepMinutes;
            // SubRecipe step fields
            Step.SubRecipe = null;
            Step.Servings = null;

            Step.Type = (byte) StepType.Generic;
            Manager.SaveChanges();
        }

        public void Update(string subRecipeName, int? servings)
        {
            var subRecipe = (Recipe) Manager.Recipes[subRecipeName];
            // SubRecipeStep fields
            Step.SubRecipe = subRecipe;
            Step.Servings = servings;
            // Generic step fields
            Step.Description = subRecipe.Name;
            Step.Resources = "";
            Step.StepHours = subRecipe.PrepHours;
            Step.StepMinutes = subRecipe.PrepMinutes;

            Step.Type = (byte) StepType.SubRecipe;
            Manager.SaveChanges();
        }

        #endregion

        public static RecipeStep New(int recipeId, int stepId)
        {
            return CreateRecipeStep(recipeId, stepId);
        }

        public override string ToString()
        {
            return string.Format("RecipeStep(Id: {0}, Type: {1})", Step.Id, Step.Type);
        }
    }

    sealed class RecipeSteps : LinkSet<RecipeStep, IRecipeStep>, IRecipeSteps
    {
        readonly bool _original;

        public RecipeSteps(Func<int> recipeId, Func<EntityCollection<RecipeStep>> steps, bool original)
            : base(steps, recipeId)
        {
            _original = original;
        }

        #region IRecipeSteps Members

        public IRecipeStep AddGeneric(string description, string resources = "", int? stepHours = null,
                                      int? stepMinutes = null)
        {
            var step = Step.CreateStep(0, (byte) StepType.Generic, _original);
            // Generic step fields
            step.Description = description;
            step.Resources = resources;
            step.StepHours = stepHours;
            step.StepMinutes = stepMinutes;
            // SubRecipe step fields
            step.SubRecipe = null;
            step.Servings = null;
            Manager.StepSet().AddObject(step);
            Manager.SaveChanges();
            return Add(RecipeStep.New(IdGetter(), step.Id));
        }

        public IRecipeStep AddSubRecipe(string subRecipeName, int? servings = null)
        {
            var step = Step.CreateStep(0, (byte) StepType.SubRecipe, _original);
            var subRecipe = (Recipe) Manager.Recipes[subRecipeName];
            // SubRecipe step fields
            step.SubRecipe = subRecipe;
            step.Servings = servings;
            // Generic step fields
            step.Description = subRecipe.Name;
            step.Resources = "";
            step.StepHours = subRecipe.PrepHours;
            step.StepMinutes = subRecipe.PrepMinutes;
            Manager.StepSet().AddObject(step);
            Manager.SaveChanges();
            return Add(RecipeStep.New(IdGetter(), step.Id));
        }

        public override IEnumerator<IRecipeStep> GetEnumerator()
        {
            return Items().Where(s => s.Original == _original).OrderBy(s => s.StepId).GetEnumerator();
        }

        public void Clear()
        {
            foreach (var id in this.Select(s => s.Id).ToList())
                Remove(id);
        }

        public override void Remove(int id)
        {
            base.Remove(id);
            var set = Manager.StepSet();
            var step = set.First(s => s.Id == id);
            set.DeleteObject(step);
            Manager.SaveChanges();
        }

        #endregion
    }

    sealed partial class RecipeTrial : IRecipeTrial
    {
        #region IRecipeTrial Members

        int IEntity.Id
        {
            get { return Trial.Id; }
        }

        string IRecipeTrial.Notes
        {
            get { return Trial.Notes; }
        }

        IRecipe IRecipeTrial.Recipe
        {
            get { return Recipe; }
        }

        #endregion

        public static RecipeTrial New(int recipeId, int trialId)
        {
            return CreateRecipeTrial(recipeId, trialId);
        }
    }

    sealed class RecipeTrials : LinkSet<RecipeTrial, IRecipeTrial>, IRecipeTrials
    {
        public RecipeTrials(Func<int> recipeId, Func<EntityCollection<RecipeTrial>> trials)
            : base(trials, recipeId)
        {
        }

        #region IRecipeTrials Members

        public IRecipeTrial Add(string notes)
        {
            var trial = Trial.CreateTrial(0, notes);
            Manager.TrialSet().AddObject(trial);
            Manager.SaveChanges();
            return Add(RecipeTrial.New(IdGetter(), trial.Id));
        }

        public override void Remove(int id)
        {
            base.Remove(id);
            var set = Manager.TrialSet();
            var trial = set.First(s => s.Id == id);
            set.DeleteObject(trial);
            Manager.SaveChanges();
        }

        #endregion
    }

    sealed partial class EventRecipeServings : IRecipeServings
    {
        #region IRecipeServings Members

        int IEntity.Id
        {
            get { return RecipeId; }
        }

        string INamedEntity.Name
        {
            get { return Recipe.Name; }
        }

        IRecipe IRecipeServings.Recipe
        {
            get { return Recipe; }
        }

        int? IRecipeServings.Servings
        {
            get { return Servings; }
        }

        public void Update(int? servings)
        {
            Servings = servings;
            Manager.SaveChanges();
        }

        #endregion

        public static EventRecipeServings New(int eventId, string recipeName, int? servings)
        {
            var recipe = Manager.Recipes[recipeName];
            var er = CreateEventRecipeServings(eventId, recipe.Id);
            er.Servings = servings ?? recipe.Servings;
            return er;
        }
    }

    sealed class RecipesServings : NamedLinkSet<EventRecipeServings, IRecipeServings>, IRecipesServings
    {
        public RecipesServings(Func<EntityCollection<EventRecipeServings>> items, Func<int> idGetter)
            : base(items, idGetter)
        {
        }

        #region IRecipesServings Members

        public IRecipeServings Add(string recipeName, int? servings)
        {
            return Add(EventRecipeServings.New(IdGetter(), recipeName, servings));
        }

        #endregion
    }

    sealed partial class RecipeGuestStat : IRecipeStat
    {
        #region IRecipeStat Members

        int IRecipeStat.Count
        {
            get { return Count; }
        }

        int IEntity.Id
        {
            get { return Recipe.Id; }
        }

        DateTime IRecipeStat.LastServed
        {
            get { return LastServed; }
        }

        string INamedEntity.Name
        {
            get { return Recipe.Name; }
        }

        IRecipe IRecipeStat.Recipe
        {
            get { return Recipe; }
        }

        public void Update(DateTime newDate)
        {
            Count++;
            if (LastServed < newDate)
                LastServed = newDate;
            Manager.SaveChanges();
        }

        #endregion
    }

    sealed class RecipeStats : NamedLinkSet<RecipeGuestStat, IRecipeStat>, IRecipeStats
    {
        public RecipeStats(Func<EntityCollection<RecipeGuestStat>> items, Func<int> idGetter)
            : base(items, idGetter)
        {
        }

        #region IRecipeStats Members

        public IRecipeStat Add(string recipeName, DateTime lastServed)
        {
            var recipeId = Manager.Recipes[recipeName].Id;
            var rs = RecipeGuestStat.CreateRecipeGuestStat(IdGetter(), recipeId, 1, lastServed);
            Add(rs);
            return rs;
        }

        #endregion
    }
}